package com.dlc.shop.seckill.common.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.dlc.shop.bean.model.Product;
import com.dlc.shop.bean.model.SeckillStockPointSku;
import com.dlc.shop.bean.model.StockPointSku;
import com.dlc.shop.bean.vo.WarehouseVO;
import com.dlc.shop.common.bo.MultiDecrementBO;
import com.dlc.shop.common.config.Constant;
import com.dlc.shop.common.constants.StockPointType;
import com.dlc.shop.common.enums.MultiStockEnum;
import com.dlc.shop.common.enums.StatusEnum;
import com.dlc.shop.common.enums.SysTypeEnum;
import com.dlc.shop.common.util.BeanUtil;
import com.dlc.shop.common.util.CacheManagerUtil;
import com.dlc.shop.manager.impl.StockManager;
import com.dlc.shop.seckill.common.constant.LockCacheNames;
import com.dlc.shop.seckill.common.dao.SeckillSkuMapper;
import com.dlc.shop.seckill.common.model.Seckill;
import com.dlc.shop.seckill.common.model.SeckillSku;
import com.dlc.shop.seckill.common.service.SeckillSkuService;
import com.dlc.shop.seckill.common.service.SeckillStockPointSkuService;
import com.dlc.shop.security.common.util.AuthUserContext;
import com.dlc.shop.service.ProductService;
import com.dlc.shop.service.SkuStockService;
import com.dlc.shop.service.WarehouseService;
import lombok.AllArgsConstructor;
import org.apache.commons.collections4.CollectionUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.aop.framework.AopContext;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * 秒杀活动sku
 *
 * @author LGH
 * @date 2019-08-28 09:36:59
 */
@Service

@AllArgsConstructor
public class SeckillSkuServiceImpl extends ServiceImpl<SeckillSkuMapper, SeckillSku> implements SeckillSkuService {

    private final SeckillSkuMapper seckillSkuMapper;

    private final CacheManagerUtil cacheManagerUtil;
    private final WarehouseService warehouseService;
    private final SeckillStockPointSkuService seckillStockPointSkuService;
    private final StockManager stockManager;
    private final RedissonClient redissonClient;
    private final SkuStockService skuStockService;
    private final ProductService productService;

    @Override
    @Cacheable(cacheNames = "SeckillSkuBySeckillId", key = "#seckillId")
    public List<SeckillSku> listSeckillSkuBySeckillId(Long seckillId) {
        List<SeckillSku> seckillSkus = seckillSkuMapper.selectList(new LambdaQueryWrapper<SeckillSku>().eq(SeckillSku::getSeckillId, seckillId));
        List<Long> seckillSkuIds = seckillSkus.stream().map(SeckillSku::getSeckillSkuId).collect(Collectors.toList());
        List<SeckillStockPointSku> stockPointSkus = seckillStockPointSkuService.list(new LambdaQueryWrapper<SeckillStockPointSku>().in(SeckillStockPointSku::getSeckillSkuId, seckillSkuIds));
        stockPointSkus.forEach(s-> s.setStock(s.getSeckillOriginStocks()));
        Map<Long, List<SeckillStockPointSku>> stockPointMap = stockPointSkus.stream().collect(Collectors.groupingBy(SeckillStockPointSku::getSeckillSkuId));
        seckillSkus.forEach(s-> s.setSeckillStockPointSkuList(stockPointMap.get(s.getSeckillSkuId())));
        return seckillSkus;
    }

    @Override
    @CacheEvict(cacheNames = "SeckillSkuBySeckillId", key = "#seckillId")
    public void removeSeckillSkuCacheBySeckillId(Long seckillId) {
        SeckillSkuService seckillSkuService = (SeckillSkuService) AopContext.currentProxy();
        List<SeckillSku> seckillSkus = seckillSkuService.listSeckillSkuBySeckillId(seckillId);
        //清除缓存
        if (CollectionUtils.isNotEmpty(seckillSkus)) {
            for (SeckillSku seckillSku : seckillSkus) {
                // 不能采用this清除缓存，因为这样没有aop
                cacheManagerUtil.evictCache("SeckillSkuById", String.valueOf(seckillSku.getSeckillSkuId()));
            }
        }
    }

    @Override
    @Cacheable(cacheNames = "SeckillSkuById", key = "#seckillSkuId")
    public SeckillSku getSeckillSkuById(Long seckillSkuId) {
        SeckillSku seckillSku = seckillSkuMapper.getBySeckillSkuId(seckillSkuId);
        seckillSku.getSeckillStockPointSkuList().sort(Comparator.comparing(SeckillStockPointSku::getType).thenComparing(SeckillStockPointSku::getStockPointType));
        return seckillSku;
    }

    @Override
    @Caching(evict = {
            @CacheEvict(cacheNames = "SeckillSkuById", key = "#seckillSkuId"),
            @CacheEvict(cacheNames = "SeckillSkuBySeckillId", key = "#seckillId"),
            @CacheEvict(cacheNames = "SeckillById", key = "#seckillId")
    })
    public void removeSeckillCacheSkuById(Long seckillSkuId,Long seckillId) {

    }

    @Override
    public void updateSeckillSkuStocks(Long seckillSkuId, Integer changeStock) {
        seckillSkuMapper.updateSeckillSkuStocks(seckillSkuId, changeStock);
    }

    @Override
    public void saveSeckillSkuStockBatch(List<SeckillSku> seckillSkuList) {
        saveBatch(seckillSkuList);
        List<SeckillStockPointSku> stockPointSkus = new ArrayList<>(Constant.INITIAL_CAPACITY);
        Map<Long, List<String>> stockPointMap = new HashMap<>();
        List<WarehouseVO> warehouseList = warehouseService.listWarehouseByShopId(AuthUserContext.get().getShopId(), SysTypeEnum.MULTISHOP.value());

        WarehouseVO defaultWarehouse = warehouseList.get(0);
        warehouseList = warehouseList.stream().filter(warehouse -> Objects.equals(SysTypeEnum.STATION.value(), warehouse.getSysType())).toList();

        for (SeckillSku seckillSku : seckillSkuList) {
            List<String> stockPointList = new ArrayList<>(seckillSku.getSeckillStockPointSkuList().size());
            Map<Long, SeckillStockPointSku> pointMap = seckillSku.getSeckillStockPointSkuList().stream()
                    .collect(Collectors.toMap(SeckillStockPointSku::getStockPointId, Function.identity()));
            // 默认库存必须要有
            if (!pointMap.containsKey(defaultWarehouse.getWarehouseId())) {
                SeckillStockPointSku seckillStockPointSku = new SeckillStockPointSku();
                seckillStockPointSku.setStock(0);
                seckillStockPointSku.setStockPointId(defaultWarehouse.getWarehouseId());
                seckillStockPointSku.setStockPointType(StockPointType.WAREHOUSE.getValue());
                seckillStockPointSku.setStatus(StatusEnum.ENABLE.value());
                seckillStockPointSku.setType(defaultWarehouse.getType());
                seckillSku.getSeckillStockPointSkuList().add(seckillStockPointSku);
            }
            // 秒杀是独立库存，所以门店信息也需要保存一份
            for (WarehouseVO warehouseVO : warehouseList) {
                SeckillStockPointSku seckillStockPointSku = pointMap.get(warehouseVO.getWarehouseId());
                if (Objects.isNull(seckillStockPointSku)) {
                    seckillStockPointSku = new SeckillStockPointSku();
                    seckillStockPointSku.setStockPointId(warehouseVO.getWarehouseId());
                    seckillStockPointSku.setStock(0);
                    seckillStockPointSku.setStockPointType(StockPointType.STATION.getValue());
                    seckillStockPointSku.setStatus(StatusEnum.ENABLE.value());
                    seckillStockPointSku.setType(warehouseVO.getType());
                    seckillSku.getSeckillStockPointSkuList().add(seckillStockPointSku);
                }
                seckillStockPointSku.setStockMode(warehouseVO.getStockMode());
            }

            seckillSku.getSeckillStockPointSkuList().forEach(s-> {
                s.setSeckillSkuId(seckillSku.getSeckillSkuId());
                s.setSkuId(seckillSku.getSkuId());
                stockPointList.add(s.getStockPointId().toString() + StrUtil.C_UNDERLINE + s.getStock());
                if (Objects.isNull(s.getType())) {
                    s.setType(1);
                }
            });
            stockPointMap.put(seckillSku.getSkuId(), stockPointList);
            stockPointSkus.addAll(seckillSku.getSeckillStockPointSkuList());
        }
        seckillStockPointSkuService.batchSave(stockPointSkus);

        // 库存到redis
        for (SeckillSku seckillSku : seckillSkuList) {
            stockManager.doZone(new MultiDecrementBO(MultiStockEnum.SECKILL,null,null,
                    seckillSku.getSeckillId(), seckillSku.getSkuId(),null,null, stockPointMap.get(seckillSku.getSkuId())));
        }

    }

    @Override
    public Map<Long, Integer> getPointStockBySeckillSkuId(Long activityId, Long skuId, List<Long> stockPointIds) {
        Map<Long, Integer> map = new HashMap<>(stockPointIds.size());
        for (Long stockPointId : stockPointIds) {
            int totalStock = 0;
            RLock rLock = redissonClient.getLock(LockCacheNames.LOCK_SECKILL_ZONE_PERFIX + skuId);
            rLock.lock();
            try {
                MultiDecrementBO decrementBO = new MultiDecrementBO(MultiStockEnum.SECKILL, null, null,
                        activityId, skuId, null, null, null);
                decrementBO.setStockPointId(stockPointId);
                totalStock = stockManager.getSeckillStock(decrementBO);
            } finally {
                rLock.unlock();
            }
            map.put(stockPointId, totalStock);
        }
        return map;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void handSeckillPointSku(Seckill seckill) {
        if (seckill == null) {
            return;
        }
        // 判断商品是否已经被删除
        Product product = productService.getProductByProdId(seckill.getProdId());
        if (Objects.isNull(product) || Objects.equals(product.getStatus(), StatusEnum.DELETE.value())) {
            return;
        }
        if (Objects.equals(seckill.getReplenish(), 1)) {
            // 补充库存
            SeckillSkuService seckillSkuService = (SeckillSkuService) AopContext.currentProxy();
            List<SeckillSku> seckillSkuList = seckillSkuService.listSeckillSkuBySeckillId(seckill.getSeckillId());
            List<SeckillStockPointSku> updateList = new ArrayList<>();
            for (SeckillSku seckillSku : seckillSkuList) {
                List<String> pointList = new ArrayList<>();
                seckillSku.getSeckillStockPointSkuList().forEach(s-> pointList.add(String.valueOf(s.getStockPointId())));
                // 获取各仓库库存
                Map<Long, Integer> pointMap = stockManager.getSeckillStockPointMap(
                        new MultiDecrementBO(MultiStockEnum.SECKILL, null, null,
                                seckill.getSeckillId(), seckillSku.getSkuId(), null, null, pointList));
                seckillSku.getSeckillStockPointSkuList().forEach(s-> {
                    s.setStock(pointMap.get(s.getStockPointId()));
                    s.setSkuId(seckillSku.getSkuId());
                    s.setProdId(seckill.getProdId());
                    s.setStatus(1);
                    s.setShopId(seckill.getShopId());
                });
                // 过滤掉已经为空的库存数据
                updateList.addAll(seckillSku.getSeckillStockPointSkuList().stream().filter(s-> s.getStock() > 0).collect(Collectors.toList()));
            }
            // 补充到仓库里面
            skuStockService.addAndUpdateStockPoint(seckill.getProdId(), BeanUtil.mapAsList(updateList, StockPointSku.class), seckill.getShopId());
        }
    }

}
